package unify;

import java.util.ArrayList;
import java.util.Collections;
import java.util.logging.Logger;

import unify.data.Episode;
import unify.data.Feed;
import unify.data.Season;
import unify.data.Settings;
import unify.data.Show;
import unify.fileio.Database;
import unify.fileio.RSSParser;
import unify.gui.GUI;
import unify.gui.SysTrayIcon;


public class ShowLibrary {
	/*
	 * Change arraylists to lists and make private.
	 * Still instantiated as arraylists. 
	 * Getters should be unmodifiable, setters shouldn't exist, instead do individual operations (add/remove/has).
	 */
	public ArrayList<Show> shows = new ArrayList<Show>();
	public ArrayList<Show> newShows = new ArrayList<Show>();
	public ArrayList<Show> onToday = new ArrayList<Show>();
	public ArrayList<Show> onTomorrow = new ArrayList<Show>();
	private ArrayList<Show> ignoredShows = new ArrayList<Show>();
	private static ShowLibrary _instance = null;
	private boolean pendingUpdates;
	private ArrayList<String> pendingNotifications;
	private Thread thread;
	private final static Logger LOGGER = Logger.getLogger(ShowLibrary.class.getName());

	private ShowLibrary() {
		super();
		this.pendingNotifications = new ArrayList<String>();
		this.pendingUpdates = false;
	}

	public static synchronized ShowLibrary getInstance() {
		if (_instance == null) {
			_instance = new ShowLibrary();
		}
		return _instance;
	}

	public Show getShow(String myTitle) {
		return getShow(this.shows, myTitle);
	}

	public Show getShow(ArrayList<Show> shows, String myTitle) {
		for (int i = 0; i < shows.size(); i++) {
			Show show = shows.get(i);
			if (show.isThisMyTitle(myTitle)) {
				return show;
			}
		}
		return null;
	}

	public Show addShow(Show show) {
		return addShow(show.getTitle(), show.getCurSeason(), show.getLastEpisode());
	}

	public Show addShow(String myTitle, int mySeason, int myEpisode) {
		Show show = new Show(myTitle);
		show.setCurSeason(mySeason);
		show.setLastEpisode(myEpisode);
		shows.add(show);
		return show;
	}

	public void doDelay() {
		LOGGER.info("Checking feeds.");
		parseFeeds();
		LOGGER.info("Done checking feeds. Checking again in " + Settings.getInstance().getDelay() + " minutes.");
		try {
			this.thread = Thread.currentThread();
			Thread.sleep(Settings.getInstance().getDelay()*60000);
		} catch (InterruptedException ie) {
			LOGGER.fine("Sleeping thread was interrupted, probably intentional.");
		}
	}

	public void parseFeeds() {
		LOGGER.fine("Initiating feed parsing...");
		if(Settings.getInstance().getFeeds().size()==0) {
			LOGGER.warning("There are no rss feeds! Add a new feed in the settings.");
		}
		populateList(onToday, Settings.getInstance().getToday());
		populateList(onTomorrow, Settings.getInstance().getTomorrow());
		for(Feed myFeed : Settings.getInstance().getFeeds()) {
			RSSParser parser = new RSSParser(myFeed);
			ArrayList<Show> rssShows = parser.parseFeed();
			checkShows(rssShows);
			if(Settings.getInstance().isFindNewShows()) {
				checkNewShows(rssShows);
			}
			LOGGER.fine("Done checking feed: " + myFeed.getLabel() + ". Feed had " + rssShows.size() + " episodes.");
		}
		if(this.pendingUpdates) {
			doUpdates();
		}
	}

	private void populateList(ArrayList<Show> showList, Feed feed) {
		showList.clear();
		RSSParser parser = new RSSParser(feed);
		ArrayList<Show> rssShows = parser.parseFeed();
		for(int i=0;i<rssShows.size();i++) {
			Show show = rssShows.get(i);
			if(getShow(show.getTitle())!=null) {
				showList.add(show);
				this.pendingUpdates = true;
			}
		}
	}

	private void checkShows(ArrayList<Show> rssShows) {
		for(int i=0;i<rssShows.size();i++) {
			Show rssShow = rssShows.get(i);
			Show show = getShow(rssShow.getTitle());
			if(show!=null) {
				for(Season rssSeason : rssShow.seasons) {
					int seasonInt = rssSeason.getNumber();
					Season season = show.findSeason(seasonInt);
					if(show.getCurSeason()<=seasonInt) {
						if(show.getCurSeason()<seasonInt) {
							season = show.addSeason(seasonInt);
							show.setLastEpisode(0);
							LOGGER.info("Found new season (" + seasonInt + ") of " + show.getTitle());
							show.setCurSeason(seasonInt);
						}
						for(Episode rssEpisode : rssSeason.getEpisodes()) {
							int episodeInt = rssEpisode.getNumber();
							Episode episode = null;
							if(season!=null) {
								episode = season.findEpisode(episodeInt);
							}
							if(show.getLastEpisode()<episodeInt && episode==null) {
								if(season==null) {
									season = show.addSeason(seasonInt);
								}
								for(int m=show.getLastEpisode()+1;m<episodeInt;m++) {
									LOGGER.info("Found new episode (" + m + ") of " + show.getTitle() + " in season " + seasonInt);
									season.addEpisode(m, " ", " ");
									pendingNotice(show.getTitle() + " (" + seasonInt + "x" + m + ") is now available.");							 
								}
								LOGGER.info("Found new episode (" + episodeInt + ") of " + show.getTitle() + " in season " + seasonInt);
								season.addEpisode(episodeInt, rssEpisode.getLink(), rssEpisode.getLinkLabel());
								pendingNotice(show.getTitle() + " (" + seasonInt + "x" + episodeInt + ") is now available.");
								show.setLastEpisode(episodeInt);
							}
							if(episode!=null) {
								int newPriority = Settings.getInstance().getFeedPriority(rssEpisode.getLinkLabel());
								int oldPriority = Settings.getInstance().getFeedPriority(episode.getLinkLabel());
								if (newPriority>oldPriority) {
									LOGGER.info("Found higher priority link for " + show.getTitle() + " (" + seasonInt + "x" + episodeInt + "), updating from " + episode.getLinkLabel() + " to " + rssEpisode.getLinkLabel());					
									episode.setLink(rssEpisode.getLink());
									episode.setLinkLabel(rssEpisode.getLinkLabel());
									pendingNotice(show.getTitle() + " (" + seasonInt + "x" + episode.getNumber() + ") is now available on " + rssEpisode.getLinkLabel() + ".");
								}
							}
						}
					}
				}
			}
		}
	}

	private void checkNewShows(ArrayList<Show> rssShows) {
		for(int i=0;i<rssShows.size();i++) {
			Show rssShow = rssShows.get(i);
			if(getShow(this.shows, rssShow.getTitle())==null && getShow(this.newShows, rssShow.getTitle())==null && getShow(this.ignoredShows, rssShow.getTitle())==null) {
				Season season = rssShow.getSeason();
				Episode episode = season.getEpisode();
				if(season.getNumber()==1 && episode.getNumber() == 1) {
					Show show = new Show(rssShow.getTitle());
					Season newSeason = show.addSeason(1);
					newSeason.addEpisode(1, episode.getLink(), episode.getLinkLabel());
					show.setCurSeason(1);
					show.setLastEpisode(1);
					this.newShows.add(show);
					this.ignoredShows.add(show);
					LOGGER.info("Found new show: " + show.getTitle());
					pendingNotice("Found new show: " + show.getTitle());
				}
			}
		}
	}

	public void doUpdates() {
		LOGGER.info("Performing updates.");
		Collections.sort(shows);
		SysTrayIcon.getInstance().generatePopup();
		if(!this.pendingNotifications.isEmpty()) {
			SysTrayIcon.getInstance().showMessages(this.pendingNotifications);
			this.pendingNotifications.clear();
			LOGGER.fine("Sending notification messages to trayicon popup.");
		}
		GUI.getInstance().refresh();
		Database.saveData();
		this.pendingUpdates = false;
	}

	private void pendingNotice(String string) {
		this.pendingUpdates = true;
		this.pendingNotifications.add(string);
	}

	public void checkFeedsNow() {
		this.thread.interrupt();
	}

	public void removeNewShow(Show show) {
		this.newShows.remove(show);
		LOGGER.fine("Removed " + show.getTitle() + " from new shows.");
	}
}